package switchviz.graphic.gauge.bargraph;

import java.util.LinkedList;
import java.util.List;

import javax.media.opengl.GL;
import javax.swing.JPanel;

import org.jdom.Element;

import switchviz.graphic.gauge.GaugeDataModel;
import switchviz.graphic.gauge.IGauge;
import switchviz.graphic.gauge.bargraph.scale.BarGraphScale;


public class BarGraph extends IGauge
{

	private class Quad
	{
		public float top;
		public float bottom;
		public float front;
		public float back;
		public float left;
		public float right;
		public int pos;
		public boolean overflow = false;
		public boolean underflow = false;
		
		public Quad(float top, float bottom, float front, float back, float left, float right, int pos, boolean overflow, boolean underflow)
		{
			this.top = top;
			this.bottom = bottom;
			this.front = front;
			this.back = back;
			this.left = left;
			this.right = right;
			this.pos = pos;
			this.overflow = overflow;
			this.underflow = underflow;
		}
	}
	private BarGraphPersistence _nodehandler = null;
	
	private List<Quad[]> _quadqueue = new LinkedList<Quad[]>();
	
	private BarGraphDataModel _bargraphdatamodel = null;
	private Quad[] qarray = null;
	private BarGraphScale _scale = null;
	private BarGraphPanel _panel = null;
	private float[][] _color = null;
	private boolean _blscale = false;
	private boolean _freeze = false;
	private boolean _enable = true;
	private int _detaillevel = 0;
	
	public BarGraph(String name, Element node)
	{
		super (name, node);
		
		init();
	}
	
	private void init()
	{
		_nodehandler = new BarGraphPersistence(_root);
		_bargraphdatamodel = _nodehandler.getDataModel();
		_scale = new BarGraphScale(_nodehandler.getScaleElement(), _bargraphdatamodel.getDataMax(), 
				 					_bargraphdatamodel.getDataMin(), _bargraphdatamodel.getDataDepth(), 
				 					_bargraphdatamodel.getRepresentation(), _enable);
	}
	
	public void draw(GL gl)
	{	
        if (isConnected() != _enable)
        {
            setEnable(isConnected());
        }
		gl.glTranslatef(-1.0f, -1.0f, 0.0f);
		gl.glScalef(1.5f, 1.5f, 1.5f);
		if(_blscale)
		{
			_scale.initialize(gl);
			_blscale = false;
		}
		_scale.draw(gl);
		if(!_enable)
			return;
		
		if(getSignals() == null) return;
		gl.glPointSize(1.0f);
		int num = getVisibleSignals().length;
		float width = 1.0f / num;
		float top = 1.0f;
		float bottom = 0.0f;
		float front = 0.01f;
		float back = _bargraphdatamodel.getDataDepth();
		float right = 0.5f;
		float left = 0.0f;
		float alphastep = back / _bargraphdatamodel.getHistorylength();
		float blockdepth = back / _bargraphdatamodel.getHistorylength();
		float datamax = _bargraphdatamodel.getDataMax();
		float datamin = _bargraphdatamodel.getDataMin();	
		back *= -1.0f;
		
		qarray = new Quad[num];
		int k = -1;
		for(int i = 0; i < num; ++i)
		{
            if ((getVisibleSignals()[i].getPriority() > _detaillevel) ||
                    !getVisibleSignals()[i].isEnable())
            {
                continue;
            }
            
			boolean overflow = false;
			boolean underflow = false;
			float val = getVisibleSignals()[i].getValue();
			++k;
			if(_bargraphdatamodel.getRepresentation() == BarGraphPanel.REP_LINEAR)
			{
				if(val > datamax)
				{
					overflow = true;
					top = 1.0f;
				}
				else if(val < datamin)
				{
					top = 0.0f;
					underflow = true;
				}
				else
					top = (val - datamin) / (datamax- datamin);
			}
			else
			{
				if(val < Float.MIN_VALUE)
					top = 0.0f;
				else if(val > datamax)
					top = 1.0f;
				else
				{
					top = (float)((Math.log10(val) -Math.log10(datamin)) /
								(Math.log10(datamax)-Math.log10(datamin)));
					//top = (float) (Math.log(val) / Math.log(datamax)); //(Math.log(top * datamax) / Math.log(datamax));
					//System.out.println("val: " + val + " top: " + top);
				}
			}
			
			qarray[i] = new Quad(top, bottom, front, back, left, right, k, overflow, underflow);
		}
		k++;
		width = 1.0f / k;
			
		if(_quadqueue.size() > _bargraphdatamodel.getHistorylength())
			_quadqueue.remove(0);
			
		if(!_freeze)
			_quadqueue.add(qarray);
			
		float[] topmem = new float[num];
		gl.glBegin(GL.GL_QUADS);
		for(int j = _quadqueue.size() - 1; j >= 0 ; --j)
		{
			qarray = _quadqueue.get(j);
			for(int i = 0; i < num; ++i)
			{
				Quad quad = qarray[i];
				
				if(quad == null) continue;
				
				quad.right = width * quad.pos + width;
				quad.left = width * quad.pos;
				quad.front = front - (_quadqueue.size() -1 - j) * blockdepth;
				quad.back = quad.front - blockdepth;
				
				if(topmem[i] <= quad.top)
					quad.bottom = topmem[i];
				else
					quad.bottom = quad.top;
				
				topmem[i] = quad.top;
				drawBlock(gl, quad, (j + 1) * alphastep, _color[i]);
				
				if(j == _quadqueue.size() - 1)
					drawFrontTopLine(gl, quad);
				
				if(quad.overflow)
					drawOverflow(gl, quad, (j + 1) * alphastep, _color[i]);
				
				if(quad.underflow)
					drawUnderflow(gl, quad, (j + 1) * alphastep, _color[i]);
			}
		}
		gl.glEnd();
	}
	
	public void initialize(GL gl) 
	{
		_scale.initialize(gl);
		
		if(getVisibleSignals() != null && getVisibleSignals().length > 0)
		{
			_color = new float[getVisibleSignals().length][3];
			for(int i = 0; i < getVisibleSignals().length; ++i)
			{
				_color[i] = getVisibleSignals()[i].getColor();
			}
				
		}
	}
	
	private void drawUnderflow(GL gl, Quad quad, float alpha, float[] color)
	{
		float height = 0.1f;
		float offset = 0.05f;
		
        gl.glColor4f(color[0], color[1], color[2], alpha);
        
        //BOTTOM
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.back);
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.back);
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.front);
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.front);
        
        //SIDE LINE
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.front);
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.front);

        //SIDE LINE
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.front);
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.front);
          
        gl.glEnd();
		gl.glBegin(GL.GL_TRIANGLES);
        
        //BACK
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.back);
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.back);
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.back);
        
        //FRONT
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.bottom - offset, quad.front);
        gl.glVertex3f( quad.left, quad.bottom - offset - height, quad.front);
        gl.glVertex3f( quad.right, quad.bottom - offset - height, quad.front);
        
		gl.glEnd();
		gl.glBegin(GL.GL_QUADS);
	}
	
	private void drawOverflow(GL gl, Quad quad, float alpha, float[] color)
	{
		float height = 0.1f;
		float offset = 0.05f;
		
        gl.glColor4f(color[0], color[1], color[2], alpha);
        
        //TOP
        gl.glVertex3f( quad.right, quad.top + offset + height, quad.back);
        gl.glVertex3f( quad.left, quad.top + offset + height, quad.back);
        gl.glVertex3f( quad.left, quad.top + offset + height, quad.front);
        gl.glVertex3f( quad.right, quad.top + offset + height, quad.front);
        
        //SIDE LINE
        gl.glVertex3f( quad.left, quad.top + offset + height, quad.front);
        gl.glVertex3f( quad.left, quad.top + offset + height, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.front);

        //SIDE LINE
        gl.glVertex3f( quad.right, quad.top + offset + height, quad.front);
        gl.glVertex3f( quad.right, quad.top + offset + height, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.back);
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.front);
          
        gl.glEnd();
		gl.glBegin(GL.GL_TRIANGLES);
        
        //BACK
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.back);
        gl.glVertex3f( quad.left, quad.top  + offset + height, quad.back);
        gl.glVertex3f( quad.right, quad.top  + offset + height, quad.back);
        
        //FRONT
        gl.glVertex3f( (quad.left + quad.right) / 2, quad.top + offset, quad.front);
        gl.glVertex3f( quad.left, quad.top  + offset + height, quad.front);
        gl.glVertex3f( quad.right, quad.top  + offset + height, quad.front);
        
		gl.glEnd();
		gl.glBegin(GL.GL_QUADS);
	}
	
	private void drawFrontTopLine(GL gl, Quad quad)
	{
		gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
        gl.glVertex3f( quad.right, quad.top + _bargraphdatamodel.getCurrvalbar(), quad.front);				
        gl.glVertex3f( quad.left, quad.top + _bargraphdatamodel.getCurrvalbar(), quad.front);					
        gl.glVertex3f( quad.left, quad.top, quad.front);					
        gl.glVertex3f( quad.right, quad.top, quad.front);
	}
	
	private void drawBlock(GL gl, Quad quad, float alpha, float[] color)
	{
        gl.glColor4f(color[0], color[1], color[2], alpha);
        
        //TOP
        gl.glVertex3f( quad.right, quad.top, quad.back);
        gl.glVertex3f( quad.left, quad.top, quad.back);
        gl.glVertex3f( quad.left, quad.top, quad.front);
        gl.glVertex3f( quad.right, quad.top, quad.front);

        //FRONT
        gl.glVertex3f( quad.right, quad.top, quad.front);
        gl.glVertex3f( quad.left, quad.top, quad.front);
        gl.glVertex3f( quad.left, quad.bottom, quad.front);
        gl.glVertex3f( quad.right, quad.bottom, quad.front);
        //SIDE LINE
        gl.glVertex3f( quad.left, quad.top, quad.front);
        gl.glVertex3f( quad.left, quad.top, quad.back);
        gl.glVertex3f( quad.left, quad.top - _bargraphdatamodel.getLinewidth(), quad.back);
        gl.glVertex3f( quad.left, quad.top - _bargraphdatamodel.getLinewidth(), quad.front);

        //SIDE LINE
        gl.glVertex3f( quad.right, quad.top, quad.back);
        gl.glVertex3f( quad.right, quad.top, quad.front);
        gl.glVertex3f( quad.right, quad.top - _bargraphdatamodel.getLinewidth(), quad.front);
        gl.glVertex3f( quad.right, quad.top - _bargraphdatamodel.getLinewidth(), quad.back);
          
        //BACK
        gl.glVertex3f( quad.right, quad.bottom, quad.back);
        gl.glVertex3f( quad.left, quad.bottom, quad.back);
        gl.glVertex3f( quad.left, quad.top, quad.back);
        gl.glVertex3f( quad.right, quad.top, quad.back);
	}
	

	public void setElement(Element node) 
	{
		_root = node;
		_nodehandler = new BarGraphPersistence(_root);
		//_scale.setElement(_nodehandler.getScaleElement());
		_bargraphdatamodel = _nodehandler.getDataModel();
		_scale = new BarGraphScale(_nodehandler.getScaleElement(), _bargraphdatamodel.getDataMax(), 
					_bargraphdatamodel.getDataMin(), _bargraphdatamodel.getDataDepth(), 
					_bargraphdatamodel.getRepresentation(), _enable);
		_blscale = true;
	}

	@Override
	public void addSetupPanels(List<JPanel> panellist) 
	{
		panellist.add(getBarGraphPanel());
		panellist.add(_scale.getSetupPanel());
		
	}

	@Override
	public void saveSetupPanels() 
	{
		_nodehandler.setElement(getBarGraphPanel().getElement());
		
		Element _scalenode = _scale.getElement();
		_nodehandler.getElement().removeContent(_nodehandler.getElement().getChild(BarGraphPersistence.SCALE));
		_nodehandler.getElement().addContent(_scalenode);
		
	}
	
	private BarGraphPanel getBarGraphPanel()
	{
		if (_panel == null)
			_panel = new BarGraphPanel(_nodehandler.getElement());
		
		return _panel;
	}

	public GaugeDataModel getGaugeDataModel() 
	{
		return _bargraphdatamodel;
	}

	@Override
	public void setEnable(boolean enable) 
	{
		_enable = enable;
		_scale = new BarGraphScale(_nodehandler.getScaleElement(), _bargraphdatamodel.getDataMax(), 
					_bargraphdatamodel.getDataMin(), _bargraphdatamodel.getDataDepth(), 
					_bargraphdatamodel.getRepresentation(), _enable);
		_blscale = true;
	}

	@Override
	public void setDetailLevel(int level) 
	{
		_detaillevel = level;
	}

	@Override
	public void setFreeze(boolean b) 
	{
		_freeze = b;
		
	}

	@Override
	public void resetData() {}
	
}
